package com.example.aspect.aop;

import cn.hutool.core.codec.Base64;
import com.example.aspect.anno.LimitRequest;
import com.example.constants.RedisConstants;
import com.example.enums.LimitRequestStrategy;
import com.example.exception.BusinessException;
import com.example.utils.IpUtil;
import com.example.utils.RedisUtil;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.TimeUnit;


/**
 * @author hello
 * @date 2023-04-12 15:53:49
 */
@Aspect
@Component
public class LimitRequestAspect {

    private final RedisUtil redisUtil;

    public LimitRequestAspect(RedisUtil redisUtil) {
        this.redisUtil = redisUtil;
    }

    /**
     * 定义切入点
     */
    @Pointcut("@annotation(com.example.aspect.anno.LimitRequest)")
    public void pointcut() {
    }

    /**
     * 前置通知：在连接点之前执行的通知
     *
     * @param joinPoint
     */
    @Before("pointcut()")
    public void doBefore(JoinPoint joinPoint) throws NoSuchMethodException {
        // 获取调用者ip
        RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
        HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
        String ipAddress = IpUtil.getIpAddress(httpServletRequest);
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取接口接口方法
        Method method = joinPoint.getTarget().getClass().getMethod(signature.getName(), signature.getParameterTypes());
        // 获取到方法名
        String methodFullName = method.getDeclaringClass().getName() + method.getName();
        //获取接口上的prevent注解
        LimitRequest annotation = method.getAnnotation(LimitRequest.class);
        //执行对应策略
        execute(annotation, ipAddress, methodFullName);
        //todo 记录日志

    }

    /**
     * 判断限流执行策略
     *
     * @param annotation
     * @param ipAddress
     * @param methodFullName
     */
    private void execute(LimitRequest annotation, String ipAddress, String methodFullName) {
        //获取限流执行策略
        LimitRequestStrategy strategy = annotation.strategy();
        if (Objects.requireNonNull(strategy) == LimitRequestStrategy.DEFAULT) {
            //执行默认策略
            defaultHandle(annotation, ipAddress, methodFullName);
        } else {
            // todo 可配置其他限流策略
            return;
        }
    }

    /**
     * 限流默认策略执行方法
     *
     * @param annotation
     * @param ipAddress
     * @param methodFullName
     */
    private void defaultHandle(LimitRequest annotation, String ipAddress, String methodFullName) {
        //加密用户ip地址
        String encode = Base64.encode(ipAddress);
        //获取访问限制时间
        long time = annotation.time();
        //获取访问限制测数
        int count = annotation.count();
        String key = RedisConstants.LIMIT_REQUEST_STRATEGY_DEFAULT + encode + ":" + methodFullName;
        //限定特定时间访问特定次数
        long increment = redisUtil.increment(key, 1);
        if (increment == 1) {
            // 如果访问次数为1，则重置访问限制时间（即redis超时时间）
            redisUtil.expire(key, time, TimeUnit.SECONDS);
        }
        if (increment > count) {
            // 访问次数超出限制，禁止访问
            String errorMessage = !StringUtils.isEmpty(annotation.message()) ? annotation.message() : "当前操作频率过快";
            throw new BusinessException(errorMessage);
        }

    }


}
